/*
 * Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
 */
package com.baidu.aip.unit;

import java.util.List;
import java.util.concurrent.TimeoutException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.baidu.aip.unit.exception.UnitError;
import com.baidu.aip.unit.listener.OnResultListener;
import com.baidu.aip.unit.model.AccessToken;
import com.baidu.aip.unit.model.Scene;
import com.baidu.aip.unit.model.response.Action;
import com.baidu.aip.unit.model.response.CommunicateResponse;
import com.baidu.aip.unit.model.response.HintQuery;
import com.baidu.aip.unit.utils.TextUtils;


public class UnitBot {

	/**
	 * 调用UNIT的超时等待时间（毫秒）
	 */
	private static final int CALL_TIMEOUT_MILLISECONDS = 15000;

	private static final Logger Log = LoggerFactory.getLogger(UnitBot.class);

    private String sessionId = "";
    private Scene curScene;
    private String accessToken;
    
    private volatile UnitError tokenError = null;
    
    private OnResultListener<CommunicateResponse> responseListener = new PrintResponseListener();

    /**
     * 构造函数，初始化网络连接
     */
    public UnitBot() {
    	APIService.getInstance().init();
    }
    
    /**
     * 构造对象时直接使用app key和secret key连接百度云平台，获取token后返回。
     * 为了防止破解app获取ak，sk，建议您把ak，sk放在服务器端。
     * @param appKey
     * @param secretKey
     * @throws TimeoutException 
     * @throws UnitError 
     */
    public UnitBot(String appKey, String secretKey) throws UnitError, TimeoutException {
    	APIService.getInstance().init();
        initAccessToken(appKey, secretKey);
    }
    
  
    /**
     * 设置处理UNIT响应消息的处理Listener
     * @param listener
     */
    public void setResponseListener(OnResultListener<CommunicateResponse> listener) {
    	this.responseListener = listener;
    }

    /**
     * 使用app key和secret key连接百度云平台，获取token后函数返回。
     * <br>为了防止破解app获取ak，sk，建议您把ak，sk放在服务器端。
     * @param appKey
     * @param secretKey
     * @throws TimeoutException 
     */
    public void initAccessToken(String appKey, String secretKey) throws UnitError, TimeoutException {
        accessToken = null;
        
        OnResultListener<AccessToken> tokenListener = new OnResultListener<AccessToken>() {
            @Override
            public void onResult(AccessToken result) {
                accessToken = result.getAccessToken();
                Log.info("AccessToken->" + result.getAccessToken());
            }

            @Override
            public void onError(UnitError error) {
                tokenError = error;
                Log.error("AccessToken->" + error.getErrorMessage());
            }
        };
        
        WaitableListenerWrapper<AccessToken> waittingListener = new WaitableListenerWrapper<>(tokenListener);
        APIService.getInstance().initAccessToken(waittingListener, appKey, secretKey);
        
        Log.info("等待返回Token...");
        waittingListener.waitListenerDone(CALL_TIMEOUT_MILLISECONDS);
        
        if (accessToken != null) {
        	Log.info("获取Token成功，token={}", accessToken);
        } else {
        	Log.error("获取Token失败");
        	throw tokenError;
        }
    }
    
    /**
     * 切换场景
     *
     * @param scene
     */
    public void setScene(Scene scene) {
        curScene = scene;
        sessionId = "";
    }

    /**
     * 发送消息给UNIT，并异步把响应报文交给预先设置的Listener处理
     * @param message 要发送的文字
     */
    public void sendMessage(String message) {
        sendMessage(message, responseListener);
    }
    
    /**
     * 发送消息给UNIT，并且等到响应报文被预先设置的Listener处理完毕后才返回
     * @param message 要发送的文字
     * @throws TimeoutException 
     */
    public void sendMessageAndWait(String message) throws TimeoutException {
    	sendMessageAndWait(message, responseListener);
    }
    
    /**
     * 发送消息给UNIT，并异步把响应报文交给参数中的Listener处理
     * @param message 要发送的文字
     * @param listener 处理相应报文的Listener
     */
    public void sendMessage(String message, final OnResultListener<CommunicateResponse> listener) {
    	// 由外部确保在调用initAccessToken后再发送消息
//        if (TextUtils.isEmpty(accessToken)) {
//        	Log.error("请先获取Token再发送消息！");
//            return;
//        }
        
        APIService.getInstance().communicate(new OnResultListener<CommunicateResponse>() {

			@Override
			public void onResult(CommunicateResponse result) {
				// 记录session
				sessionId = result.getSessionId();
				
				// 交给真正的Listener处理
				listener.onResult(result);
			}

			@Override
			public void onError(UnitError error) {
				listener.onError(error);				
			}
        }
        
        , curScene.getId(), message, sessionId);
    }
    
    /**
     * 发送消息给UNIT，并且等到响应报文被参数中的Listener处理完毕后才返回
     * @param message 要发送的文字
     * @throws TimeoutException 
     */
    public void sendMessageAndWait(String message, final OnResultListener<CommunicateResponse> listener) throws TimeoutException {
    	WaitableListenerWrapper<CommunicateResponse> waittingListener = new WaitableListenerWrapper<>(listener);
        sendMessage(message, waittingListener);
        waittingListener.waitListenerDone(CALL_TIMEOUT_MILLISECONDS);
    }
    
    /**
     * 重置Session
     */
    public void clearSession() {
    	this.sessionId = "";
    }
   
    /**
     * 默认的响应Listener。仅打印响应信息到控制台。
     *
     */
    private class PrintResponseListener implements OnResultListener<CommunicateResponse> {

		@Override
		public void onResult(CommunicateResponse result) {
            List<Action> actionList = result.getActionList();
            int n = 0;
            for (Action action : actionList) {
            	Log.info("第{}个Action:", n);
            	printAction(action);
            }
		}

		@Override
		public void onError(UnitError error) {
			Log.error("Unit Error: id={}, message={}", error.getErrorCode(), error.getMessage());
		}
		
		private void printAction(Action action) {
			Log.info("actionId={}", action.getActionId());
            if (!TextUtils.isEmpty(action.getSay())) {
                Log.info(action.getSay());
                for (HintQuery hint : action.getHintList()) {
                	Log.info("--hint:{}", hint.getHintQuery());
                }
            }
		}
    }
    
    /**
     * Listener的包装类，当Listener被异步回调时可以进行等候。
     * 类中有状态字段，故不要重复使用同一个实例，避免错乱。
     * 
     * @param <T>
     */
    private class WaitableListenerWrapper<T> implements OnResultListener<T> {
    	
    	private OnResultListener<T> originListener = null;
    	private Object lock = new Object();
    	private volatile boolean handled = false;
    	private UnitError error = null;
    	
    	public WaitableListenerWrapper(final OnResultListener<T> listener) {
    		this.originListener = listener;
    	}

		@Override
		public void onResult(T result) {
			originListener.onResult(result);
			this.error = null;
			this.handled = true;
			synchronized(lock){
				lock.notifyAll();
	        }
		}

		@Override
		public void onError(UnitError error) {
			originListener.onError(error);
			this.error = error;
			this.handled = true;
			synchronized(lock){
				lock.notifyAll();
	        }
		}
		
		/**
		 * 等待Listener处理完毕或直到超时退出
		 * @param timeout 等待超时时间，单位毫秒。0表示无限等待。
		 * @throws TimeoutException 如果超时且Listener未执行完毕，则抛出异常
		 */
		public void waitListenerDone(long timeout) throws TimeoutException {
			synchronized(lock){
				try {
					lock.wait(timeout);
				} catch (InterruptedException e) {
					throw new RuntimeException(e);
				}
	        }
			if (!this.handled) {
				// 超时返回
				throw new TimeoutException();
			}
		}
		
		/**
		 * 等待Listener处理完毕
		 * @throws TimeoutException 如果超时且Listener未执行完毕，则抛出异常
		 */
		@SuppressWarnings("unused")
		public void waitListenerDone() throws TimeoutException {
			waitListenerDone(0);
		}
		
		/**
		 * 返回异步调用时出现的Error。如果没有，则返回null。
		 * @return
		 */
		@SuppressWarnings("unused")
		public UnitError getError() {
			return this.error;
		}
    }

}
